package com.aaron.guide.nettystudy.client;

import com.aaron.guide.nettystudy.coding.KyroDecoder;
import com.aaron.guide.nettystudy.coding.KyroEncoder;
import com.aaron.guide.nettystudy.entity.RpcRequest;
import com.aaron.guide.nettystudy.entity.RpcResponse;
import com.aaron.guide.nettystudy.handler.NettyClientHandler;
import com.aaron.guide.nettystudy.serialize.KyroSerializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName NettyClient
 * @description:
 * @author: 荣燊
 * @create: 2021-05-14 22:23
 **/
public class NettyClient {
    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
    private final String host;
    private final int port;
    private static final Bootstrap b; //启动类

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    static {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        b = new Bootstrap();
        KyroSerializer kyroSerializer = new KyroSerializer();

        b.group(eventExecutors)
                .channel(NioSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //入栈逻辑，将字节转化为对象（KyroDecoder内部使用的是反序列化方法），再传输给下一个入栈处理逻辑。
                        //这里是服务端给客户端返回的消息，所以要转化为RpcResponse格式
                        socketChannel.pipeline().addLast(new KyroDecoder(kyroSerializer, RpcResponse.class));
                        //出栈逻辑，将对象转化为字节（KyroEncoder内部使用的是序列化方法）。放入到ByteBuf中
                        socketChannel.pipeline().addLast(new KyroEncoder(kyroSerializer, RpcRequest.class));
                        //入栈逻辑，再做自定义的Handler，得到第一个入栈传过来的数据。
                        //先log出来，之后再放到AttributeMap（绑定Channel）上。下面的sendMessage()需要用到它作为返回值。
                        socketChannel.pipeline().addLast(new NettyClientHandler());
                    }
                });
    }

    //该方法发送之后，会返回AttributeMap上的值（RpcResponse对象的）
    public RpcResponse sendMessage(RpcRequest rpcRequest) {
        try {
            //异步调用，先直接返回一个ChannelFuture
            //这里是使用sync方法直接阻塞在这
            ChannelFuture f = b.connect(host, port).sync();
            logger.info("client connect  {}", host + ":" + port);
            //通过FutureChannel获取通道Channel
            Channel futureChannel = f.channel();
            logger.info("send message");
            //
            if (futureChannel != null) {
                //future-listener监听机制，这里的writeAndFlush是不是直接调用出栈方法了？
                futureChannel.writeAndFlush(rpcRequest).addListener(future -> {
                    if (future.isSuccess()) {
                        logger.info("client send message: [{}]", rpcRequest.toString());
                    } else {
                        logger.error("Send failed:", future.cause());
                    }
                });
                //阻塞等待 ，直到Channel关闭
                futureChannel.closeFuture().sync();
                //将服务端返回的数据也就是RpcResponse对象取出。入栈的时候已经赋值了。
                AttributeKey<RpcResponse> key = AttributeKey.valueOf("rpcResponse");
                return futureChannel.attr(key).get();
            }
        } catch (InterruptedException e) {
            logger.error("occur exception when connect server:", e);
        }
        return null;
    }

    public static void main(String[] args) {
        //将rpcRequest对象发送给服务端
        RpcRequest rpcRequest = RpcRequest.builder().interfaceName("interface").methodName("hello").build();

        NettyClient nettyClient = new NettyClient("127.0.0.1", 8889);

        for (int i = 0; i < 3; i++) {
            nettyClient.sendMessage(rpcRequest);
        }
        //方法：Obj sendMessage(obj)，传入对象1返回对象2
        RpcResponse rpcResponse = nettyClient.sendMessage(rpcRequest);

        System.out.println(rpcResponse.toString());
    }
}
